package org.sixtynine.stock.dao.impl;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import javax.persistence.Query;
import javax.persistence.TypedQuery;

import org.hibernate.Criteria;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.criterion.Criterion;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Projections;
import org.sixtynine.stock.dao.AbstractDao;
import org.sixtynine.stock.dto.GridPramDTO;
import org.sixtynine.stock.dto.Page;
import org.sixtynine.stock.entity.BaseEntity;
import org.springframework.beans.factory.annotation.Autowired;

public abstract class AbstractDaoImpl<E, I extends Serializable> implements
		AbstractDao<E, I> {

	public static final int BATCH_SIZE = 50;

	private Class<E> entityClass;

	protected AbstractDaoImpl(Class<E> entityClass) {
		this.entityClass = entityClass;
	}

	@Autowired
	private SessionFactory sessionFactory;

	public Session getCurrentSession() {
		return sessionFactory.getCurrentSession();
	}

	@Override
	public void saveList(List<BaseEntity> objs) {
		Session session = null;
		Transaction tx = null;
		try {
			
			session = sessionFactory.openSession();
			tx = session.beginTransaction();

			for (int i = 0; i < objs.size(); i++) {
				session.save(objs.get(i));
				if (i % BATCH_SIZE == 0) {
					// flush a batch of inserts and release memory:
					session.flush();
					session.clear();
				}
			}
		} finally {
			tx.commit();
			session.close();
		}
	}

	@Override
	public E findById(I id) {
		return (E) getCurrentSession().get(entityClass, id);
	}

	@Override
	public E findById(I id, Class<E> clazz) {
		return (E) getCurrentSession().get(clazz, id);
	}

	@Override
	public void saveOrUpdate(E e) {
		getCurrentSession().saveOrUpdate(e);
	}

	@Override
	public void delete(E e) {
		getCurrentSession().delete(e);
	}

	@Override
	public List<E> findByCriteria(Criterion criterion) {
		Criteria criteria = getCurrentSession().createCriteria(entityClass);
		criteria.add(criterion);
		return criteria.list();
	}

	@Override
	public List<E> findByCriteria(Criterion criterion, Class<E> clazz) {
		Criteria criteria = getCurrentSession().createCriteria(clazz);
		criteria.add(criterion);
		return criteria.list();
	}

	@Override
	public List<E> findLatestRecords(int numResuts, Class<E> clazz,
			String orderBy, Criterion... criterion) {
		Criteria criteria = getCurrentSession().createCriteria(clazz);
		criteria.addOrder(Order.desc(getRecordOrder(orderBy)));
		criteria.setMaxResults(numResuts);

		for (Criterion c : criterion) {
			criteria.add(c);
		}

		return criteria.list();
	}

	public String getRecordOrder(String orderBy) {
		if (orderBy == null) {
			return "id";
		}

		return orderBy;
	}

	@Override
	public List<E> findByCriteria(Criteria criteria) {
		return criteria.list();
	}

	@Override
	public List<E> findByCriteria(Class<E> clazz, Criterion... criterion) {
		Criteria criteria = getCurrentSession().createCriteria(clazz);
		for (Criterion c : criterion) {
			criteria.add(c);
		}

		return criteria.list();
	}

	@Override
	public List<E> findAll(Class<E> clazz) {
		return findByCriteria(clazz);
	}

	@Override
	public Page<E> getPagedData(TypedQuery<E> query, Query totalQuery,
			int start, int count) {

		Collection<E> resultList = query.setFirstResult(start)
				.setMaxResults(count).getResultList();
		Long totalNoOfRecs = (Long) totalQuery.getSingleResult();

		if (resultList == null) {
			resultList = new ArrayList<E>();
		}

		Page<E> page = new Page<E>(totalNoOfRecs, start, resultList.size(),
				count, resultList);
		return page;
	}

	@Override
	public Page<E> getPagedData(TypedQuery<E> query) {
		Collection<E> resultList = query.getResultList();
		if (resultList == null) {
			resultList = new ArrayList<E>();
		}
		Page<E> page = new Page<E>(resultList.size(), 0, resultList.size(),
				resultList);
		return page;

	}

	@Override
	public Page<E> getPagedData(TypedQuery<E> query, Query totalQuery,
			GridPramDTO gridPramDTO) {
		Integer pageIndex = gridPramDTO.getPage() - 1;
		Integer rows = gridPramDTO.getRows();
		Integer start = pageIndex * rows;
		Integer count = rows;
		Collection<E> resultList = query.setFirstResult(start)
				.setMaxResults(count).getResultList();
		Long totalNoOfRecs = (Long) totalQuery.getSingleResult();

		if (resultList == null) {
			resultList = new ArrayList<E>();
		}

		Page<E> page = new Page<E>(totalNoOfRecs, start, resultList.size(),
				rows, resultList);
		return page;
	}

	@Override
	public Page<E> findAllWithPaging(Class<E> clazz, GridPramDTO gridPramDTO) {
		return findByCriteria(clazz, gridPramDTO);
	}

	@Override
	public Page<E> findByCriteria(Criteria criteria, GridPramDTO gridPramDTO) {
		Integer pageIndex = gridPramDTO.getPage() - 1;
		Integer rows = gridPramDTO.getRows();
		Integer start = pageIndex * rows;
		Integer count = rows;

		criteria.setFirstResult(start);
		criteria.setMaxResults(count);

		Collection<E> resultList = criteria.list();
		Integer totalResult = gridPramDTO._getTotalNumberOfRecords();
		if (totalResult == -1) {
			totalResult = ((Number) criteria.setProjection(
					Projections.rowCount()).uniqueResult()).intValue();
		}

		Page<E> page = new Page<E>(totalResult, start, resultList.size(), rows,
				resultList);
		return page;

	}

	@Override
	public Page<E> findByCriteria(Class<E> clazz, GridPramDTO gridPramDTO,
			Criterion... criterion) {

		Integer pageIndex = gridPramDTO.getPage() - 1;
		Integer rows = gridPramDTO.getRows();
		Integer start = pageIndex * rows;
		Integer count = rows;

		Criteria criteria = getCurrentSession().createCriteria(clazz);
		for (Criterion c : criterion) {
			criteria.add(c);
		}

		criteria.setFirstResult(start);
		criteria.setMaxResults(count);

		Collection<E> resultList = criteria.list();
		Integer totalResult = gridPramDTO._getTotalNumberOfRecords();
		if (totalResult == -1) {
			totalResult = ((Number) criteria.setProjection(
					Projections.rowCount()).uniqueResult()).intValue();
		}

		Page<E> page = new Page<E>(totalResult, start, resultList.size(), rows,
				resultList);
		return page;
	}

}
